﻿using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using FairyGUI;

namespace GameFramework.UI.FairyGUI
{
    /// <summary>
    /// FairyGUI 动效拓展方法。
    /// </summary>
    public static class FairyGUITransitionExtensions
    {
        public enum TransitionCancelBehaviour
        {
            Kill,
            Complete,
            CancelAwait,
        }

        /// <summary>
        /// 将FairyGUI的Transition转换为UniTask
        /// </summary>
        public static UniTask PlayAsUniTask(this Transition transition, int times, float delay, float startTime, float endTime, PlayCompleteCallback originCallback, TransitionCancelBehaviour cancelBehaviour = TransitionCancelBehaviour.Kill, CancellationToken cancellationToken = default)
        {
            if (transition == null)
                throw new ArgumentNullException(nameof(transition));

            return new UniTask(TransitionTaskSource.Create(transition, times, delay, startTime, endTime, originCallback, cancelBehaviour, cancellationToken, out var token), token);
        }

        /// <summary>
        /// 将FairyGUI的Transition转换为UniTask
        /// </summary>
        public static UniTask PlayAsUniTask(this Transition transition, int times, float delay, PlayCompleteCallback originCallback, TransitionCancelBehaviour cancelBehaviour = TransitionCancelBehaviour.Kill, CancellationToken cancellationToken = default)
        {
            if (transition == null)
                throw new ArgumentNullException(nameof(transition));

            return new UniTask(TransitionTaskSource.Create(transition, times, delay, 0, -1, originCallback, cancelBehaviour, cancellationToken, out var token), token);
        }

        /// <summary>
        /// 将FairyGUI的Transition转换为UniTask
        /// </summary>
        public static UniTask PlayAsUniTask(this Transition transition, PlayCompleteCallback originCallback, TransitionCancelBehaviour cancelBehaviour = TransitionCancelBehaviour.Kill, CancellationToken cancellationToken = default)
        {
            if (transition == null)
                throw new ArgumentNullException(nameof(transition));

            return new UniTask(TransitionTaskSource.Create(transition, 1, 0, 0, -1, originCallback, cancelBehaviour, cancellationToken, out var token), token);
        }

        /// <summary>
        /// 将FairyGUI的Transition转换为UniTask
        /// </summary>
        public static UniTask PlayAsUniTask(this Transition transition, TransitionCancelBehaviour cancelBehaviour = TransitionCancelBehaviour.Kill, CancellationToken cancellationToken = default)
        {
            if (transition == null)
                throw new ArgumentNullException(nameof(transition));

            return new UniTask(TransitionTaskSource.Create(transition, 1, 0, 0, -1, null, cancelBehaviour, cancellationToken, out var token), token);
        }

        /// <summary>
        /// 将FairyGUI的Transition转换为UniTask
        /// </summary>
        public static UniTask PlayReverseAsUniTask(this Transition transition, int times, float delay, PlayCompleteCallback originCallback, TransitionCancelBehaviour cancelBehaviour = TransitionCancelBehaviour.Kill, CancellationToken cancellationToken = default)
        {
            if (transition == null)
                throw new ArgumentNullException(nameof(transition));

            return new UniTask(TransitionTaskSource.CreateReverse(transition, times, delay, originCallback, cancelBehaviour, cancellationToken, out var token), token);
        }

        /// <summary>
        /// 将FairyGUI的Transition转换为UniTask
        /// </summary>
        public static UniTask PlayReverseAsUniTask(this Transition transition, PlayCompleteCallback originCallback, TransitionCancelBehaviour cancelBehaviour = TransitionCancelBehaviour.Kill, CancellationToken cancellationToken = default)
        {
            if (transition == null)
                throw new ArgumentNullException(nameof(transition));

            return new UniTask(TransitionTaskSource.CreateReverse(transition, 1, 0, originCallback, cancelBehaviour, cancellationToken, out var token), token);
        }

        /// <summary>
        /// 将FairyGUI的Transition转换为UniTask
        /// </summary>
        public static UniTask PlayReverseAsUniTask(this Transition transition, TransitionCancelBehaviour cancelBehaviour = TransitionCancelBehaviour.Kill, CancellationToken cancellationToken = default)
        {
            if (transition == null)
                throw new ArgumentNullException(nameof(transition));

            return new UniTask(TransitionTaskSource.CreateReverse(transition, 1, 0, null, cancelBehaviour, cancellationToken, out var token), token);
        }

        private sealed class TransitionTaskSource : IUniTaskSource, ITaskPoolNode<TransitionTaskSource>
        {
            private static TaskPool<TransitionTaskSource> pool;
            private TransitionTaskSource m_NextNode;
            public ref TransitionTaskSource NextNode => ref m_NextNode;

            static TransitionTaskSource()
            {
                TaskPool.RegisterSizeGetter(typeof(TransitionTaskSource), () => pool.Size);
            }

            private Transition m_Transition;
            private UniTaskCompletionSourceCore<AsyncUnit> m_Core;

            private CancellationToken m_CancellationToken;
            private TransitionCancelBehaviour m_CancelBehaviour;

            private PlayCompleteCallback m_OriginalCompleteAction;

            readonly PlayCompleteCallback m_OnCompleteCallbackDelegate;
            readonly Action m_OnTokenCanceledCallbackDelegate;

            public TransitionTaskSource()
            {
                m_OnCompleteCallbackDelegate = OnCompleteCallbackDelegate;
                m_OnTokenCanceledCallbackDelegate = OnTokenCanceledCallbackDelegate;
            }

            public static IUniTaskSource Create(Transition transition, int times, float delay, float startTime, float endTime, PlayCompleteCallback originCallback, TransitionCancelBehaviour cancelBehaviour, CancellationToken cancellationToken, out short token)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token);
                }

                if (!pool.TryPop(out var result))
                {
                    result = new TransitionTaskSource();
                }

                result.m_Transition = transition;

                result.m_CancelBehaviour = cancelBehaviour;
                result.m_CancellationToken = cancellationToken;
                result.m_OriginalCompleteAction = originCallback;

                TaskTracker.TrackActiveTask(result, 3);

                token = result.m_Core.Version;
                
                transition.Play(times, delay, startTime, endTime, result.m_OnCompleteCallbackDelegate);

                cancellationToken.Register(result.m_OnTokenCanceledCallbackDelegate);
                
                return result;
            }

            public static IUniTaskSource CreateReverse(Transition transition, int times, float delay, PlayCompleteCallback originCallback, TransitionCancelBehaviour cancelBehaviour, CancellationToken cancellationToken, out short token)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token);
                }

                if (!pool.TryPop(out var result))
                {
                    result = new TransitionTaskSource();
                }

                result.m_Transition = transition;

                result.m_CancelBehaviour = cancelBehaviour;
                result.m_CancellationToken = cancellationToken;
                result.m_OriginalCompleteAction = originCallback;

                TaskTracker.TrackActiveTask(result, 3);

                token = result.m_Core.Version;
                
                transition.PlayReverse(times, delay, result.m_OnCompleteCallbackDelegate);

                cancellationToken.Register(result.m_OnTokenCanceledCallbackDelegate);
                
                return result;
            }

            private void OnCompleteCallbackDelegate()
            {
                m_OriginalCompleteAction?.Invoke();
                m_Core.TrySetResult(AsyncUnit.Default);
            }

            private void OnTokenCanceledCallbackDelegate()
            {
                if (!m_CancellationToken.IsCancellationRequested)
                {
                    return;
                }

                switch (m_CancelBehaviour)
                {
                    case TransitionCancelBehaviour.Kill:
                        m_Transition.Stop(false, false);
                        break;
                    case TransitionCancelBehaviour.Complete:
                        m_Transition.Stop(true, false);
                        break;
                    case TransitionCancelBehaviour.CancelAwait:
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
                
                m_Core.TrySetCanceled(m_CancellationToken);
            }

            public void GetResult(short token)
            {
                try
                {
                    m_Core.GetResult(token);
                }
                finally
                {
                    TryReturn();
                }
            }

            public UniTaskStatus GetStatus(short token)
            {
                return m_Core.GetStatus(token);
            }

            public UniTaskStatus UnsafeGetStatus()
            {
                return m_Core.UnsafeGetStatus();
            }

            public void OnCompleted(Action<object> continuation, object state, short token)
            {
                m_Core.OnCompleted(continuation, state, token);
            }

            bool TryReturn()
            {
                TaskTracker.RemoveTracking(this);
                m_Core.Reset();

                m_Transition = default;
                m_CancellationToken = default;
                m_OriginalCompleteAction = default;

                return pool.TryPush(this);
            }
        }
    }
}